﻿// SyntheticDataGenerator.Core.PatternSet
//
// (c) 2011 Arthur Pitman
//
// Part of Market-Basket Synthetic Data Generator
// A C# adaptation of the IBM Quest Market-Basket Synthetic Data Generator
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// (Version 2) as published by the Free Software Foundation.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License (Version 2) along with this program; if not, write to 
// the Free Software Foundation, Inc., 
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// or see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SyntheticDataGenerator.Generators;
using System.IO;

using SyntheticDataGenerator.ParameterSets;

namespace SyntheticDataGenerator.Core
{
	/// <summary>
	/// A set of patterns
	/// </summary>
	public class PatternSet
	{
		/// <summary>
		/// The <see cref="ItemSet"/> used to create patterns
		/// </summary>
		protected ItemSet items;

		/// <summary>
		/// Patterns in the <see cref="PatternSet"/>
		/// </summary>
		protected Pattern[] patterns;

		/// <summary>
		/// Number of patterns
		/// </summary>
		public int PatternCount { get; set; }

		/// <summary>
		/// Cumulative probabilities of patterns
		/// </summary>
		public double[] CumulativeProbabilities { get; set; }

		/// <summary>
		/// Initializes a new <see cref="PatternSet"/> instance, creating patterns from the specified <see cref="ItemSet"/>
		/// </summary>
		/// <param name="items"></param>
		/// <param name="patternParameters"></param>
		/// <param name="repetitionLevel"></param>
		/// <param name="repetitionVariance"></param>
		protected PatternSet(ItemSet items, PatternParameters patternParameters, double repetitionLevel, double repetitionVariance)
		{
			this.items = items;
			PatternCount = patternParameters.PatternCount;

			var confidenceGenerator = new NormalGenerator(patternParameters.Confidence, patternParameters.ConfidenceVariance);
			var frequencyGenerator = new ExponentialGenerator();
			var correlationGenerator = new ExponentialGenerator();
			var lengthGenerator = new PoissonGenerator(patternParameters.AverageLength - 1);
			var repeatGenerator = new NormalGenerator(repetitionLevel, repetitionVariance);
			var uniformGenerator = new UniformGenerator();
				
			patterns = new Pattern[PatternCount];
			for (int i = 0; i < PatternCount; i++)
			{
				int repeatedItemCount = 0;
				patterns[i] = new Pattern(1 + lengthGenerator.NextInt(), frequencyGenerator.Next(), confidenceGenerator.Next());

				// fill correlated items
				if (patternParameters.Correlation > 0 && i > 0)
				{
					// correlated patterns
					// each pattern has some items same as the previous pattern
					repeatedItemCount = (int)(patterns[i].Items.Length * patternParameters.Correlation * correlationGenerator.Next() + 0.5);
					if (repeatedItemCount > patterns[i - 1].Items.Length)
						repeatedItemCount = patterns[i - 1].Items.Length;
					if (repeatedItemCount > patterns[i].Items.Length)
						repeatedItemCount = patterns[i].Items.Length;

					// choose num_same items at random from previous pattern
					var shuffleIndexs = uniformGenerator.ChooseRandomSubset(patterns[i - 1].Items.Length, repeatedItemCount).ToArray();
					for (int j = 0; j < repeatedItemCount; j++)
						patterns[i].Items[j] = patterns[i - 1].Items[shuffleIndexs[j]];
				}
				else
				{
					// no correlation
					repeatedItemCount = 0;
				}

				if (repetitionLevel == 0)
				{
					// fill remaining items at random
					for (int j = repeatedItemCount; j < patterns[i].Items.Length; j++)
						patterns[i].Items[j] = items.GetItem();
				}
				else
				{
					// some items are repetitions
					double repetition = repeatGenerator.Next();
					for (int j = repeatedItemCount; j < patterns[i].Items.Length; j++)
					{
						// if item should be repeated, use the previous one, otherwise pick a random one
						if ((j > 0) && (uniformGenerator.Next() < repetition))
							patterns[i].Items[j] = patterns[i].Items[(int)(j * uniformGenerator.Next())];
						else
							patterns[i].Items[j] = items.GetItem();
					}
				}			
			}
		}

		/// <summary>
		/// Initializes a new <see cref="PatternSet"/> instance
		/// </summary>
		/// <param name="itemCount"></param>
		/// <param name="patternParameters"></param>
		/// <param name="repetitionLevel"></param>
		/// <param name="repetitionVariance"></param>
		public PatternSet(int itemCount, PatternParameters patternParameters, double repetitionLevel = 0, double repetitionVariance = 0.2)
			: this(new ItemSet(itemCount), patternParameters, repetitionLevel, repetitionVariance)
		{
			CalculateProbabilities();
		}

		/// <summary>
		/// Normalizes and calculates cumulative probabilites
		/// </summary>
		protected void CalculateProbabilities()
		{
			// normalize probabilites
			double total = 0;
			for (int i = 0; i < PatternCount; i++)
				total += patterns[i].Probability;
			for (int i = 0; i < PatternCount; i++)
				patterns[i].Probability /= total;

			// calulate cumulative probabilities
			CumulativeProbabilities = new double[PatternCount];
			CumulativeProbabilities[0] = patterns[0].Probability;
			for (int i = 1; i < PatternCount; i++)
			{
				CumulativeProbabilities[i] = CumulativeProbabilities[i - 1] + patterns[i].Probability;
			}			
		}

		/// <summary>
		/// Returns a <see cref="Pattern"/> by index
		/// </summary>
		/// <param name="i"></param>
		/// <returns></returns>
		public virtual Pattern GetPattern(int i)
		{
			return patterns[i];
		}
		

		/// <summary>
		/// Writes patterns to the specified <see cref="TextWriter"/>
		/// </summary>
		/// <param name="writer"></param>
		public void Write(TextWriter writer)
		{
			items.Write(writer);
			writer.WriteLine();

			writer.WriteLine("Patterns:");
			// write patterns in sorted order
			var sortedPatterns = from p in patterns
								 orderby p.Probability descending
								 select p;
			foreach (var p in sortedPatterns)
				p.Write(writer);			
		}

	}
}
